package com.lime49.lockcrypt;

import com.lime49.lockcrypt.ui.GradientJPanel;
import java.awt.Color;
import java.util.HashSet;
import java.util.ResourceBundle;
import java.util.regex.Pattern;

/**
 * Calculates the strength of a password based on the inclusion and repiiton of certain characters and length
 */
public class PWStrengthChecker {
    private static boolean debug = false;
    
    public static int checkStrength(String score) {
        int nScore = 0,
            nLength = 0,
            
            // Character type counts
            nAlphaUC = 0,
            nAlphaLC = 0,
            nNumber = 0,
            nSymbol = 0,
            nMidChar = 0,
            nRepChar = 0,
            nConsecAlphaUC = 0,
            nConsecAlphaLC = 0,
            nConsecNumber = 0,
            nConsecSymbol = 0,
            nConsecCharType = 0,
            nSeqAlpha = 0,
            nSeqNumber = 0,
            nSeqChar = 0,
            
            // Bonus multipliers
            nMultLength = 4,
            nMultNumber = 4,
            nMultSymbol = 6,
            nMultMidChar = 2,
            
            // Penalty multipliers
            nMultConsecAlphaUC = 2,
            nMultConsecAlphaLC = 2,
            nMultConsecNumber = 2,
            nMultSeqAlpha = 3,
            nMultSeqNumber = 3,
            
            // Buffers to check for repeated subsequent characters
            nTmpAlphaUC = 0,
            nTmpAlphaLC = 0,
            nTmpNumber = 0,
            nTmpSymbol = 0;
        String sAlphas = "abcdefghijklmnopqrstuvwxyz";
        String sNumerics = "01234567890";
	
        // http://www.passwordmeter.com/
	if(score.length() > 0) {
		nScore = score.length() * nMultLength;
		nLength = score.length();
                char currentChar, currentCharLower;
		/* Loop through password to check for Symbol, Numeric, Lowercase and Uppercase pattern matches */
		for(int a=0; a < nLength; a++) {
                        currentChar = score.charAt(a);
			if(Character.isUpperCase(currentChar)) {
				if(nTmpAlphaUC > 0) { if((nTmpAlphaUC + 1) == a) { nConsecAlphaUC++; nConsecCharType++; } }
				nTmpAlphaUC = a;
				nAlphaUC++;
                        } else if(Character.isLowerCase(currentChar)) {
				if(nTmpAlphaLC > 0) { if((nTmpAlphaLC + 1) == a) { nConsecAlphaLC++; nConsecCharType++; } }
				nTmpAlphaLC = a;
				nAlphaLC++;
			} else if(Character.isDigit(currentChar)) {
				if(a > 0 && a < (nLength - 1)) { nMidChar++; }
				if(nTmpNumber > 0) { if((nTmpNumber + 1) == a) { nConsecNumber++; nConsecCharType++; } }
				nTmpNumber = a;
				nNumber++;
                        } else if(!Character.isLetter(currentChar) && !Character.isDigit(currentChar)) {
				if(a > 0 && a < (nLength - 1)) { nMidChar++; }
				if(nTmpSymbol > 0) { if((nTmpSymbol + 1) == a) { nConsecSymbol++; nConsecCharType++; } }
				nTmpSymbol = a;
				nSymbol++;
			}
			/* Check for repeated characters */
                        currentCharLower = Character.toLowerCase(currentChar);
			for(int b=0; b < nLength; b++) {
				if(currentCharLower == Character.toLowerCase(score.charAt(b)) && a != b) { nRepChar++; }
			}
		}
                
		/* Check for sequential alpha string patterns (forward and reverse) */
		for(int s=0; s < 23; s++) {
			String sFwd = sAlphas.substring(s,s+3);
			String sRev = new StringBuffer(sFwd).reverse().toString();
			if(score.toLowerCase().indexOf(sFwd) != -1 || score.toLowerCase().indexOf(sRev) != -1) { nSeqAlpha++; nSeqChar++;}
		}
		
		/* Check for sequential numeric string patterns (forward and reverse) */
		for(int s=0; s < 8; s++) {
			String sFwd = sNumerics.substring(s,s+3);
			String sRev = new StringBuffer(sFwd).reverse().toString();
			if(score.toLowerCase().indexOf(sFwd) != -1 || score.toLowerCase().indexOf(sRev) != -1) { nSeqNumber++; nSeqChar++;}
		}
		
                /* Assign bonuses */
		if(nAlphaUC > 0 && nAlphaUC < nLength) {	
			nScore = nScore + ((nLength - nAlphaUC) * 2);
		}
		if(nAlphaLC > 0 && nAlphaLC < nLength) {	
			nScore = nScore + ((nLength - nAlphaLC) * 2);
		}
		if(nNumber > 0 && nNumber < nLength) {	
			nScore = nScore + (nNumber * nMultNumber);
		}
		if(nSymbol > 0) {	
			nScore = nScore + (nSymbol * nMultSymbol);
		}
		if(nMidChar > 0) {	
			nScore = nScore + (nMidChar * nMultMidChar);
		}
                
                /* Penalties */
		if((nAlphaLC > 0 || nAlphaUC > 0) && nSymbol == 0 && nNumber == 0) {  // Only Letters
			nScore = nScore - (nLength/2);
		}
		if(nAlphaLC == 0 && nAlphaUC == 0 && nSymbol == 0 && nNumber > 0) {  // Only Numbers
			nScore = nScore - (nLength/2);
		}
		if(nRepChar > 0) {  // Same character exists more than once
			nScore = nScore - ((nRepChar * nRepChar)/2);
		}
		if(nConsecAlphaUC > 0) {  // Consecutive Uppercase Letters exist
			nScore = nScore - (nConsecAlphaUC * nMultConsecAlphaUC);
		}
		if(nConsecAlphaLC > 0) {  // Consecutive Lowercase Letters exist
			nScore = nScore - (nConsecAlphaLC * nMultConsecAlphaLC);
		}
		if(nConsecNumber > 0) {  // Consecutive Numbers exist
			nScore = nScore - (nConsecNumber * nMultConsecNumber); 
		}
		if(nSeqAlpha > 0) {  // Sequential alpha strings exist (3 characters or more)
			nScore = nScore - (nSeqAlpha * nMultSeqAlpha);
		}
		if(nSeqNumber > 0) {  // Sequential numeric strings exist (3 characters or more)
			nScore = nScore - (nSeqNumber * nMultSeqNumber);
		}
                if(debug) {
                    String sAlphaUC = "0";
                    String sAlphaLC = "0";
                    String sNumber = "0";
                    String sSymbol = "0";
                    String sMidChar = "0";
                    String sAlphasOnly = "0";
                    String sNumbersOnly = "0";
                    String sRepChar = "0";
                    String sConsecAlphaUC = "0";
                    String sConsecAlphaLC = "0";
                    String sConsecNumber = "0";
                    String sSeqAlpha = "0";
                    String sSeqNumber = "0";
                    
                    /* Bonuses */
                    if(nAlphaUC > 0 && nAlphaUC < nLength) {	
                            sAlphaUC = "+ " + ((nLength - nAlphaUC) * 2);
                    }
                    if(nAlphaLC > 0 && nAlphaLC < nLength) {	
                            sAlphaLC = "+ " + ((nLength - nAlphaLC) * 2);
                    }
                    if(nNumber > 0 && nNumber < nLength) {	
                            sNumber = "+ " + (nNumber * nMultNumber);
                    }
                    if(nSymbol > 0) {	
                            sSymbol = "+ " + (nSymbol * nMultSymbol);
                    }
                    if(nMidChar > 0) {	
                            sMidChar = "+ " + (nMidChar * nMultMidChar);
                    }
                    
                    /* Point deductions for poor practices */
                    if((nAlphaLC > 0 || nAlphaUC > 0) && nSymbol == 0 && nNumber == 0) {  // Only Letters
                            sAlphasOnly = "- " + nLength;
                    }
                    if(nAlphaLC == 0 && nAlphaUC == 0 && nSymbol == 0 && nNumber > 0) {  // Only Numbers
                            sNumbersOnly = "- " + nLength;
                    }
                    if(nRepChar > 0) {  // Same character exists more than once
                            sRepChar = "- " + nRepChar;
                    }
                    if(nConsecAlphaUC > 0) {  // Consecutive Uppercase Letters exist
                            sConsecAlphaUC = "- " + (nConsecAlphaUC * nMultConsecAlphaUC);
                    }
                    if(nConsecAlphaLC > 0) {  // Consecutive Lowercase Letters exist
                            sConsecAlphaLC = "- " + (nConsecAlphaLC * nMultConsecAlphaLC);
                    }
                    if(nConsecNumber > 0) {  // Consecutive Numbers exist
                            sConsecNumber = "- " + (nConsecNumber * nMultConsecNumber);
                    }
                    if(nSeqAlpha > 0) {  // Sequential alpha strings exist (3 characters or more)
                            sSeqAlpha = "- " + (nSeqAlpha * nMultSeqAlpha);
                    }
                    if(nSeqNumber > 0) {  // Sequential numeric strings exist (3 characters or more)
                            sSeqNumber = "- " + (nSeqNumber * nMultSeqNumber);
                    }
                    System.out.println("---Password Analysis---");
                    System.out.println("Score: "+nScore + "%");
                    System.out.println("--Bonuses--");
                    System.out.println("Length: +" + nScore);
                    System.out.println("Contains Uppercase letters: "+sAlphaUC);
                    System.out.println("Contains Lowercase letters: "+sAlphaLC);
                    System.out.println("Contains Numbers: "+sNumber);
                    System.out.println("Contains Symbols: "+sSymbol);
                    System.out.println("Contains numbers/symbols: "+sMidChar);
                    System.out.println("\n--Penalties--");
                    System.out.println("Only letters: "+sAlphasOnly);
                    System.out.println("Only numbers: "+sNumbersOnly);
                    System.out.println("Repeated characters: "+sRepChar);
                    System.out.println("Subsequents uppercase letters: "+sConsecAlphaUC);
                    System.out.println("Subsequent lowercase letters: "+sConsecAlphaLC);
                    System.out.println("Subsequent numbers: "+sConsecNumber);
                    System.out.println("Sequential letters: "+sSeqAlpha);
                    System.out.println("Sequential numbers: "+sSeqNumber);   
                }
	}
        if(nScore > 100) { nScore = 100; } else if(nScore < 0) { nScore = 0; }
        return nScore;
    }
    
    /**
      * Returns a textual rating of password strength
      * @param score The score (0-100)
      * @param lang The ResourceBundle containing descriptions for PW_VERYWEAK, PW_WEAK, PW_GOOD, PW_STRONG and PW_VERYSTRONG
      * @return A textual representation of the strength
      */
    public static String getStrengthDescription(int score, ResourceBundle lang) {
        String complexity = "";
        if(score > 100) { score = 100; } else if(score < 0) { score = 0; }
        if(score < 20) { 
            complexity = lang.getString("PW_VERYWEAK");
        } else if(score < 40) { 
            complexity = lang.getString("PW_WEAK");
        } else if(score < 60) { 
            complexity = lang.getString("PW_GOOD");
        } else if(score < 80) { 
            complexity = lang.getString("PW_STRONG");
        } else if(score <= 100) { 
            complexity = lang.getString("PW_VERYSTRONG");
        }
        return complexity;
    }
    
    /**
     * Calculate a color for a password of the specified strength
     * @param percentage The strength of the password
     * @return The colour at the specified percentage (between red and green)
     */
    public static Color calcHuePercentage(int percentage) {
        if(percentage <= 0) {
            percentage = 0;
        } else if(percentage >= 100) {
            percentage = 100;
        }
        Float res = (Float)0.23F*(new Float(percentage)/100);
        return Color.getHSBColor(res, 0.8F, 0.9F);
    }
    
    /**
     * Sets the gradient of a GradientJPanel to reflect the strength of a password
     * @param label The label to set
     * @param pwStrength The strength percentage
     */
    public static void setPanelGradient(GradientJPanel panel, int pwStrength) {
        int nStart = pwStrength, 
            nEnd = pwStrength + 10;
        if(nStart < 0) {
            nStart = 0;
        }
        if(nEnd >= 100) {
            nEnd = 100;
        }
        panel.setGradient(PWStrengthChecker.calcHuePercentage(nStart), PWStrengthChecker.calcHuePercentage(nEnd));
    }
    
    /**
     * Calculates the strength in bits of a password
     * @param password The password
     * @return The bit strength of the password
     */
    public static int calculateBitStrength(String password) {
        int poolsize = 0,
            pwLength = password.length();
        if(pwLength == 0) {
            return 0;
        } else {
            boolean hasUpper = false,
                    hasLower = false,
                    hasNumeric = false,
                    hasSymbols = false;
            char currentChar;
            HashSet<Character> pool = new HashSet<Character>();
            for(int i=0;i<pwLength;i++) {
                currentChar = password.charAt(i);
                if(Character.isUpperCase(currentChar)) {
                    hasUpper = true;
                } else if(Character.isLowerCase(currentChar)) {
                    hasLower = true;
                } else if(Character.isDigit(currentChar)) {
                    hasNumeric = true;
                } else {
                    hasSymbols = true;
                    if(pool.add(currentChar)) {
                        poolsize++;
                    }
                }
            }
            if(hasUpper) {
                poolsize += 26;
            }
            if(hasLower) {
                poolsize += 26;
            }
            if(hasNumeric) {
                poolsize += 10;
            }
            if(hasSymbols) {
                poolsize += 10; // too many symbols to add, so add each one to the pool (in the for loop above)
            }
            
            double bitStrength = Math.log(Math.pow(poolsize, password.length())) / Math.log(2);
//            System.out.println(poolsize+" uniques, "+bitStrength+" bits");
            return (int)Math.round(bitStrength);
        }
    }
}
